/*
 *  PhoneInteraction.cpp
 *  iActivator
 *
 *  Created by The Operator on 20/07/07.
 *  Copyright 2007 The Operator. All rights reserved.
 *
 * This software is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License version 2 for more details
 */

#include <sys/types.h>
#include <sys/stat.h>

#if defined(WIN32)
#include <windows.h>

#define sleep(x) Sleep((x)*1000)
#define S_IRGRP 0
#define S_IXGRP 0
#define S_IROTH 0
#define S_IXOTH 0

#endif

#include <string.h>
#include <unistd.h>

#include "PhoneInteraction/PhoneInteraction.h"
#include "PhoneInteraction/UtilityFunctions.h"
#include "MobileDevice/MobileDevice.h"


#if 1
typedef unsigned int (*t_performOperation)(struct am_restore_device *rdev,
                                           CFDictionaryRef op) __attribute__ ((regparm(2)));
typedef unsigned int (*t_sendFileToDevice)(struct am_recovery_device *rdev,
										   CFStringRef file) __attribute__ ((regparm(2)));
typedef int (*t_socketForPort)(struct am_restore_device *rdev, unsigned int port)
__attribute__ ((regparm(2)));
typedef unsigned int (*t_sendCommandToDevice)(struct am_recovery_device *rdev,
                                              CFStringRef cmd) __attribute__ ((regparm(2)));
#else
typedef unsigned int (*t_performOperation)(struct am_restore_device *rdev,
                                           CFDictionaryRef op);
typedef unsigned int (*t_sendFileToDevice)(struct am_recovery_device *rdev,
										   CFStringRef file);
typedef int (*t_socketForPort)(struct am_restore_device *rdev, unsigned int port);
typedef unsigned int (*t_sendCommandToDevice)(struct am_recovery_device *rdev,
                                              CFStringRef cmd);
#endif


static PhoneInteraction *g_phoneInteraction = NULL;
static char *g_symlinks[] =
{
	"/etc",
	"/mach",
	"/private/etc/resolv.conf",
	"/private/var/db/localtime",
	"/System/Library/Frameworks/IOKit.framework/IOKit",
	"/System/Library/Frameworks/IOKit.framework/Resources",
	"/System/Library/Frameworks/IOKit.framework/Versions/Current",
	"/System/Library/Frameworks/MultitouchSupport.framework/MultitouchSupport",
	"/System/Library/Frameworks/MultitouchSupport.framework/Resources",
	"/System/Library/Frameworks/MultitouchSupport.framework/Versions/Current",
	"/System/Library/Frameworks/OfficeImport.framework/OfficeImport",
	"/System/Library/Frameworks/OfficeImport.framework/Resources",
	"/System/Library/Frameworks/OfficeImport.framework/Versions/Current",
	"/System/Library/Frameworks/System.framework/System",
	"/System/Library/Frameworks/System.framework/Resources",
	"/System/Library/Frameworks/System.framework/Versions/Current",
	"/System/Library/Frameworks/System.framework/Versions/B/System",
	"/tmp",
	"/usr/lib/libbz2.1.0.dylib",
	"/usr/lib/libbz2.dylib",
	"/usr/lib/libc.dylib",
	"/usr/lib/libcharset.1.0.0.dylib",
	"/usr/lib/libcharset.dylib",
	"/usr/lib/libcrypto.dylib",
	"/usr/lib/libcurses.dylib",
	"/usr/lib/libdbm.dylib",
	"/usr/lib/libdl.dylib",
	"/usr/lib/libdns_sd.dylib",
	"/usr/lib/libedit.dylib", 
	"/usr/lib/libform.dylib",
	"/usr/lib/libiconv.2.2.0.dylib",
	"/usr/lib/libiconv.dylib",
	"/usr/lib/libicucore.dylib",
	"/usr/lib/libinfo.dylib",
	"/usr/lib/libIOKit.A.dylib",
	"/usr/lib/libIOKit.dylib",
	"/usr/lib/libipsec.dylib",
	"/usr/lib/libkvm.dylib",
	"/usr/lib/libm.dylib",
	"/usr/lib/libncurses.dylib",
	"/usr/lib/libobjc.dylib",
	"/usr/lib/libpoll.dylib",
	"/usr/lib/libpthread.dylib",
	"/usr/lib/librpcsvc.dylib",
	"/usr/lib/libsqlite3.0.8.6.dylib",
	"/usr/lib/libsqlite3.dylib",
	"/usr/lib/libssl.dylib",
	"/usr/lib/libstdc++.6.dylib",
	"/usr/lib/libSystem.dylib",
	"/usr/lib/libtidy.dylib",
	"/usr/lib/libxml2.dylib",
	"/usr/lib/libz.1.1.3.dylib",
	"/usr/lib/libz.1.dylib",
	"/usr/lib/libz.dylib",
	"/var"
};
static char *g_symlinkOriginals[] =
{
	"/private/etc",
	"/mach_kernel",
	"/private/var/run/resolv.conf",
	"/usr/share/zoneinfo/US/Pacific",
	"/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit",
	"/System/Library/Frameworks/IOKit.framework/Versions/A/Resources",
	"/System/Library/Frameworks/IOKit.framework/Versions/A",
	"/System/Library/Frameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport",
	"/System/Library/Frameworks/MultitouchSupport.framework/Versions/A/Resources",
	"/System/Library/Frameworks/MultitouchSupport.framework/Versions/A",
	"/System/Library/Frameworks/OfficeImport.framework/Versions/A/OfficeImport",
	"/System/Library/Frameworks/OfficeImport.framework/Versions/A/Resources",
	"/System/Library/Frameworks/OfficeImport.framework/Versions/A",
	"/usr/lib/libSystem.B.dylib",
	"/System/Library/Frameworks/System.framework/Versions/B/Resources",
	"/System/Library/Frameworks/System.framework/Versions/B",
	"/usr/lib/libSystem.B.dylib",
	"/var/tmp", 
	"/usr/lib/libbz2.1.0.3.dylib",
	"/usr/lib/libbz2.1.0.3.dylib",
	"/usr/lib/libSystem.B.dylib",
	"/usr/lib/libcharset.1.dylib",
	"/usr/lib/libcharset.1.dylib",
	"/usr/lib/libcrypto.0.9.7.dylib",
	"/usr/lib/libncurses.5.4.dylib",
	"/usr/lib/libSystem.B.dylib",
	"/usr/lib/libSystem.B.dylib",
	"/usr/lib/libSystem.B.dylib",
	"/usr/lib/libedit.2.dylib",
	"/usr/lib/libform.dylib",
	"/usr/lib/libiconv.2.dylib",
	"/usr/lib/libiconv.2.dylib",
	"/usr/lib/libicucore.A.dylib",
	"/usr/lib/libSystem.B.dylib",
	"/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit",
	"/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit",
	"/usr/lib/libipsec.A.dylib",
	"/usr/lib/libSystem.B.dylib",
	"/usr/lib/libSystem.B.dylib",
	"/usr/lib/libncurses.5.4.dylib",
	"/usr/lib/libobjc.A.dylib",
	"/usr/lib/libSystem.B.dylib",
	"/usr/lib/libSystem.B.dylib",
	"/usr/lib/libSystem.B.dylib",
	"/usr/lib/libsqlite3.0.dylib",
	"/usr/lib/libsqlite3.0.dylib",
	"/usr/lib/libssl.0.9.7.dylib",
	"/usr/lib/libstdc++.6.0.4.dylib",
	"/usr/lib/libSystem.B.dylib",
	"/usr/lib/libtidy.A.dylib",
	"/usr/lib/libxml2.2.dylib",
	"/usr/lib/libz.1.2.3.dylib",
	"/usr/lib/libz.1.2.3.dylib",
	"/usr/lib/libz.1.2.3.dylib",
	"/private/var"
};

static int g_numSymlinks = 55;
static t_socketForPort _socketForPort;
static t_performOperation _performOperation;
static t_sendCommandToDevice _sendCommandToDevice;
static t_sendFileToDevice _sendFileToDevice;


void deviceNotificationCallback(am_device_notification_callback_info *info)
{
#ifdef DEBUG
	CFShow(CFSTR("deviceNotificationCallback"));
#endif

	if (info->msg == ADNCI_MSG_CONNECTED) {

		if (g_phoneInteraction->m_waitingForRecovery) {
			g_phoneInteraction->cancelJailbreak(true);
		}

		if (g_phoneInteraction->m_switchingToRestoreMode) {
			g_phoneInteraction->m_switchingToRestoreMode = false;
			g_phoneInteraction->restoreModeStarted(info->dev);
		}
		else {

			if (g_phoneInteraction->m_finishingJailbreak) {
				g_phoneInteraction->jailbreakFinished();
			}

			g_phoneInteraction->connectToPhone(info->dev);
		}

	}
	else if (info->msg == ADNCI_MSG_DISCONNECTED) {

		if (g_phoneInteraction->m_inRestoreMode) {
			g_phoneInteraction->restoreModeFinished(info->dev);
		}
		else {
			g_phoneInteraction->setConnected(false);
		}

	}
#ifdef DEBUG
	else {
		CFStringRef cfsr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("info->msg = %d"), info->msg);

		if (cfsr) {
			CFShow(cfsr);
			CFRelease(cfsr);
		}

	}
#endif

}

void dfuConnectNotificationCallback(am_recovery_device *dev)
{
#ifdef DEBUG
	CFShow(CFSTR("dfuConnectNotificationCallback"));
#endif
}

void dfuDisconnectNotificationCallback(am_recovery_device *dev)
{
#ifdef DEBUG
	CFShow(CFSTR("dfuDisconnectNotificationCallback"));
#endif
}

void recoveryProgressCallback(unsigned int progress_number, unsigned int opcode)
{
#ifdef DEBUG
	CFShow(CFSTR("recoveryProgressCallback"));
#endif

	if (opcode == 9) {
		g_phoneInteraction->sendCommandToDevice(g_phoneInteraction->m_recoveryDevice,
												CFSTR("setenv boot-args rd=md0 -progress"));

		// alternative boot args for restore mode boot spew
		//g_phoneInteraction->sendCommandToDevice(g_phoneInteraction->m_recoveryDevice,
		//						CFSTR("setenv boot-args rd=md0 -v"));
	}
	else {
#ifdef DEBUG
		CFStringRef cfsr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("Progress number: %d, opcode: %d"), progress_number, opcode);
		
		if (cfsr) {
			CFShow(cfsr);
			CFRelease(cfsr);
		}
#endif

	}

}

void recoveryConnectNotificationCallback(am_recovery_device *dev)
{
#ifdef DEBUG
	CFShow(CFSTR("recoveryConnectNotificationCallback"));
#endif

	if (g_phoneInteraction->m_waitingForRecovery) {
		g_phoneInteraction->recoveryModeStarted(dev);
	}

}

void recoveryDisconnectNotificationCallback(am_recovery_device *dev)
{
#ifdef DEBUG
	CFShow(CFSTR("recoveryDisconnectNotificationCallback"));
#endif
	g_phoneInteraction->recoveryModeFinished(dev);
}

PhoneInteraction::PhoneInteraction(void (*statusFunc)(const char*, bool),
								   void (*notifyFunc)(int, const char*))
{
	m_statusFunc = statusFunc;
	m_notifyFunc = notifyFunc;
	m_iPhone = NULL;
	m_recoveryDevice = NULL;
	m_connected = false;
	m_inRecoveryMode = false;
	m_switchingToRestoreMode = false;
	m_inRestoreMode = false;
	m_jailbroken = false;
	m_finishingJailbreak = false;
	m_hAFC = NULL;
	m_firmwarePath = NULL;
	m_waitingForRecovery = false;

	setupPrivateFunctions();

	if (m_statusFunc) {
		(*m_statusFunc)("Disconnected: waiting for iPhone", true);
	}
	
}

PhoneInteraction::~PhoneInteraction()
{
	disconnectFromPhone();

	if (m_firmwarePath != NULL) {
		free(m_firmwarePath);
		m_firmwarePath = NULL;
	}

}

PhoneInteraction* PhoneInteraction::getInstance(void (*statusFunc)(const char*, bool),
												void (*notifyFunc)(int, const char*))
{

	if (g_phoneInteraction == NULL) {
		g_phoneInteraction = new PhoneInteraction(statusFunc, notifyFunc);
		g_phoneInteraction->subscribeToNotifications();
	}

	return g_phoneInteraction;
}

#if defined(WIN32)
void PhoneInteraction::setupPrivateFunctions()
{
	HMODULE hGetProcIDDLL;
	hGetProcIDDLL = GetModuleHandle("iTunesMobileDevice.dll");
	
	if (!hGetProcIDDLL) {
		(*m_notifyFunc)(NOTIFY_WIN32_INITIALIZATION_FAILED, "Error obtaining handle to iTunesMobileDevice.dll");
		return;
	}
	
	//int __usercall sendCommandToDevice<eax>(AMRecoveryModeDevice *amrecd<esi>,void *cfsCommand) @ 10009290
	//the address of AMRestorePerformRecoveryModeRestore is 10009F30
	_sendCommandToDevice= (t_sendCommandToDevice)((char*)GetProcAddress(hGetProcIDDLL, "AMRestorePerformRecoveryModeRestore")-0x10009F30+0x10009290);

	//int __usercall sendFileToDevice<eax>(AMRecoveryModeDevice *amrd<ecx>,void *filename) @ 10009410
	//the address of AMRestorePerformRecoveryModeRestore is 10009F30
	_sendFileToDevice= (t_sendFileToDevice)((char*)GetProcAddress(hGetProcIDDLL, "AMRestorePerformRecoveryModeRestore")-0x10009F30+0x10009410);

	//int __usercall performOperation<eax>(AMRestoreModeDevice *device<esi>,void *message<ebx>) @ 100129C0
	//the address of AMRestorePerformRecoveryModeRestore is 10009F30
	_performOperation= (t_performOperation)((char*)GetProcAddress(hGetProcIDDLL, "AMRestorePerformRecoveryModeRestore")-0x10009F30+0x100129C0);

	// @ 0x10012830 i think
	//the address of AMRestorePerformRecoveryModeRestore is 10009F30
	_socketForPort= (t_socketForPort)((char*)GetProcAddress(hGetProcIDDLL, "AMRestorePerformRecoveryModeRestore")-0x10009F30+0x10012830);
}
#elif defined(__APPLE__) && defined(__POWERPC__)
void PhoneInteraction::setupPrivateFunctions()
{
	_performOperation = (t_performOperation)0x3c3a0e14;
	_socketForPort = (t_socketForPort)0x3c3a0644;
	_sendCommandToDevice = (t_sendCommandToDevice)0x3c3a517c;
	_sendFileToDevice = (t_sendFileToDevice)0x3c3a52dc;
}
#elif defined(__APPLE__)
void PhoneInteraction::setupPrivateFunctions()
{
	_performOperation = (t_performOperation)0x3c39fa4b;
	_socketForPort = (t_socketForPort)0x3c39f36c;
	_sendCommandToDevice = (t_sendCommandToDevice)0x3c3a3e3b;
	_sendFileToDevice = (t_sendFileToDevice)0x3c3a4087;
}
#endif

void PhoneInteraction::subscribeToNotifications()
{
	struct am_device_notification *notif;

	if (AMDeviceNotificationSubscribe(deviceNotificationCallback, 0, 0, 0, &notif)) {
		(*m_notifyFunc)(NOTIFY_INITIALIZATION_FAILED, "Error registering for callbacks from iPhone");
		return;
	}

	if (AMRestoreRegisterForDeviceNotifications(dfuConnectNotificationCallback,
												recoveryConnectNotificationCallback,
												dfuDisconnectNotificationCallback,
												recoveryDisconnectNotificationCallback,
												0, NULL)) {
		(*m_notifyFunc)(NOTIFY_INITIALIZATION_FAILED, "Error registering for callbacks from iPhone");
		return;
	}
	
	
	(*m_notifyFunc)(NOTIFY_INITIALIZATION_SUCCESS, "Notification subscription success!");
	return;
}

void PhoneInteraction::connectToPhone(am_device* iPhone)
{

	if (isConnected()) return;
	
	m_iPhone = iPhone;

	if (m_statusFunc) {
		(*m_statusFunc)("Connecting to iPhone...", true);
	}

	if ( AMDeviceConnect(m_iPhone) ) { 
		setConnected(false);
		(*m_notifyFunc)(NOTIFY_CONNECTION_FAILED, "Connection failed: can't connect");
		return;
	}
	
	if ( !AMDeviceIsPaired(m_iPhone) ) {
		setConnected(false);
		(*m_notifyFunc)(NOTIFY_CONNECTION_FAILED, "Connection failed: pairing issue");
		return;
	}
	
	if ( AMDeviceValidatePairing(m_iPhone) ) {
		setConnected(false);
		(*m_notifyFunc)(NOTIFY_CONNECTION_FAILED, "Connection failed: pairing not valid");
		return;
	}

	if ( AMDeviceStartSession(m_iPhone) ) {
		setConnected(false);
		(*m_notifyFunc)(NOTIFY_CONNECTION_FAILED, "Error starting session");
		return;
	}

	m_jailbroken = false;
	afc_connection *hAFC;

	if ( AMDeviceStartService(m_iPhone, AMSVC_AFC2, &hAFC, NULL) ) {

		if ( AMDeviceStartService(m_iPhone, AMSVC_AFC, &hAFC, NULL) ) {
			setConnected(false);
			(*m_notifyFunc)(NOTIFY_CONNECTION_FAILED, "Error connecting to AFC service");
			return;
		}

	}
	else {
		// AFC2 is running.  We're jailbroken.
		m_jailbroken = true;
	}

	if (AFCConnectionOpen(hAFC, 0, &m_hAFC)) {
		setConnected(false);
		(*m_notifyFunc)(NOTIFY_CONNECTION_FAILED, "Error opening connection to AFC service");
		return;
	}

	if (!m_jailbroken) {
		// For older jailbreak compatibility: test to see if we can open a system
		// file.  If so, then we're jailbroken.
		afc_file_ref rAFC;

		if (!AFCFileRefOpen(m_hAFC, "/private/etc/master.passwd", 1, &rAFC)) {
			m_jailbroken = true;
			AFCFileRefClose(m_hAFC, rAFC);
		}

	}

	setConnected(true);
	(*m_notifyFunc)(NOTIFY_CONNECTION_SUCCESS, "Connection success!");
}

void PhoneInteraction::disconnectFromPhone()
{
	
	if (isConnected()) {
		AFCConnectionClose(m_hAFC);
		AMDeviceDisconnect(m_iPhone);
		AMDeviceStopSession(m_iPhone);
		m_jailbroken = false;
		setConnected(false);
	}
	
}

void PhoneInteraction::setConnected(bool connected)
{
	m_connected = connected;

	if (m_connected) {
		(*m_notifyFunc)(NOTIFY_CONNECTED, "Connected");
	}
	else {
		(*m_notifyFunc)(NOTIFY_DISCONNECTED, "Disconnected");
	}

}

bool PhoneInteraction::isConnected()
{
	return m_connected;
}

void PhoneInteraction::activate(const char* filename)
{

	if (!isConnected()) {
		(*m_notifyFunc)(NOTIFY_ACTIVATION_FAILED, "Can't activate when no phone is connected.");
		return;
	}

	if (m_statusFunc) {
		(*m_statusFunc)("Activating...", true);
	}

	CFStringRef fileString = CFStringCreateWithCString(kCFAllocatorDefault, filename, kCFStringEncodingUTF8);
	CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, fileString, kCFURLPOSIXPathStyle, false);
	CFReadStreamRef stream= CFReadStreamCreateWithFile(kCFAllocatorDefault, url);

	Boolean opened = CFReadStreamOpen(stream);

	if (opened == FALSE) {
		(*m_notifyFunc)(NOTIFY_ACTIVATION_FAILED, "Error creating dictionary from given plist file.");
		CFRelease(fileString);
		CFRelease(url);
		CFRelease(stream);
		return;
	}

	CFStringRef errString;
	CFPropertyListFormat format;
	CFDictionaryRef activationDict = (CFDictionaryRef)CFPropertyListCreateFromStream(kCFAllocatorDefault,
																					 stream, 0,
																					 kCFPropertyListMutableContainersAndLeaves,
																					 &format,
																					 &errString);
	CFReadStreamClose(stream);
	CFRelease(fileString);
	CFRelease(url);
	CFRelease(stream);
	
	if (errString != NULL) {
		(*m_notifyFunc)(NOTIFY_ACTIVATION_FAILED, "Error creating dictionary from given plist file.");
		return;
	}

	CFMutableDictionaryRef activationRecord = (CFMutableDictionaryRef)CFDictionaryGetValue(activationDict,
																						   CFSTR("ActivationRecord"));

	if (activationRecord == NULL) {

		// it may be an activation record from a phone with version 1.0.1 firmware,
		// so check for the AccountToken in the original dictionary
		if (!CFDictionaryContainsKey(activationDict, CFSTR("AccountToken"))) {
			CFRelease(activationDict);
			(*m_notifyFunc)(NOTIFY_ACTIVATION_FAILED, "Error obtaining activation record from dictionary.");
			return;
		}

		activationRecord = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, activationDict);

		if (activationRecord == NULL) {
			CFRelease(activationDict);
			(*m_notifyFunc)(NOTIFY_ACTIVATION_FAILED, "Error obtaining activation record from dictionary.");
			return;
		}

	}

	int retval;

	if ( (retval = AMDeviceActivate(m_iPhone, activationRecord)) ) {
		CFMutableStringRef resultStr = CFStringCreateMutable(kCFAllocatorDefault, 0);
		CFStringAppendFormat(resultStr, NULL, CFSTR("Activation failed with code %d."), retval);

		CFIndex length = CFStringGetLength(resultStr);
		char resultCStr[length+1];
		CFStringGetCString(resultStr, resultCStr, length+1, kCFStringEncodingUTF8);
		(*m_notifyFunc)(NOTIFY_ACTIVATION_FAILED, resultCStr);
		CFRelease(resultStr);
	}
	else {
		CFStringRef result = AMDeviceCopyValue(m_iPhone, 0, CFSTR("ActivationState"));
		CFMutableStringRef resultStr = CFStringCreateMutable(kCFAllocatorDefault, 0);
		CFStringAppend(resultStr, CFSTR("Activation succeeded."));

		if (result) {
			CFStringAppend(resultStr, CFSTR("\n\nActivation result: "));
			CFStringAppend(resultStr, result);
		}
		
		CFIndex length = CFStringGetLength(resultStr);
		char resultCStr[length+1];
		CFStringGetCString(resultStr, resultCStr, length+1, kCFStringEncodingUTF8);
		(*m_notifyFunc)(NOTIFY_ACTIVATION_SUCCESS, resultCStr);
		CFRelease(resultStr);
	}

	CFRelease(activationDict);
}

void PhoneInteraction::deactivate()
{

	if (!isConnected()) {
		(*m_notifyFunc)(NOTIFY_DEACTIVATION_FAILED, "Can't deactivate when no phone is connected.");
		return;
	}

	if (m_statusFunc) {
		(*m_statusFunc)("Deactivating...", true);
	}

	int retval;

	if ( (retval = AMDeviceDeactivate(m_iPhone)) ) {
		CFMutableStringRef resultStr = CFStringCreateMutable(kCFAllocatorDefault, 0);
		CFStringAppendFormat(resultStr, NULL, CFSTR("Deactivation failed with code %d."), retval);
		CFIndex length = CFStringGetLength(resultStr);
		char resultCStr[length+1];
		CFStringGetCString(resultStr, resultCStr, length+1, kCFStringEncodingUTF8);
		(*m_notifyFunc)(NOTIFY_DEACTIVATION_FAILED, resultCStr);
		CFRelease(resultStr);
	}
	else {
		CFStringRef result = AMDeviceCopyValue(m_iPhone, 0, CFSTR("ActivationState"));
		CFMutableStringRef resultStr = CFStringCreateMutable(kCFAllocatorDefault, 0);
		CFStringAppend(resultStr, CFSTR("Dectivation succeeded."));

		if (result) {
			CFStringAppend(resultStr, CFSTR("\n\nDeactivation result: "));
			CFStringAppend(resultStr, result);
		}
		
		CFIndex length = CFStringGetLength(resultStr);
		char resultCStr[length+1];
		CFStringGetCString(resultStr, resultCStr, length+1, kCFStringEncodingUTF8);
		(*m_notifyFunc)(NOTIFY_DEACTIVATION_SUCCESS, resultCStr);
		CFRelease(resultStr);
	}

}

bool PhoneInteraction::isPhoneActivated()
{

	if (!isConnected()) {
		return false;
	}

	CFStringRef result = AMDeviceCopyValue(m_iPhone, 0, CFSTR("ActivationState"));

	if (result == NULL) {
		return false;
	}

	if (CFStringCompare(result, CFSTR("Activated"), 0) != kCFCompareEqualTo) {
		return false;
	}

	return true;
}

bool PhoneInteraction::putData(void *data, int len, char *dest, int failureMsg, int successMsg)
{
	
	if (!isConnected()) {
		
		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Can't write a file when no phone is connected.");
		}
		
		return false;
	}

	afc_file_ref rAFC;
	
	if (AFCFileRefOpen(m_hAFC, dest, 3, &rAFC)) {

		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Error opening destination file for writing on phone.");
		}
		
		return false;
	}
	
	if (AFCFileRefWrite(m_hAFC, rAFC, data, len)) {
		AFCFileRefClose(m_hAFC, rAFC);
		
		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Error writing destination file on phone.");
		}
		
		return false;
	}
	
	AFCFileRefClose(m_hAFC, rAFC);
	
	if (successMsg) {
		(*m_notifyFunc)(successMsg, "File successfully written to phone.");
	}
	
	return true;
}

bool PhoneInteraction::putFile(const char *src, char *dest, int failureMsg, int successMsg)
{

	if (!isConnected()) {

		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Can't write a file when no phone is connected.");
		}

		return false;
	}
	
	struct stat st;
	FILE* fp = fopen(src, "r");
	
	if (fp == NULL) {

		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Error opening source file.");
		}

		return false;
	}
	
	if (stat(src, &st) < 0) {
		fclose(fp);

		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Error getting source file size.");
		}

		return false;
	}
	
	void *data = malloc(st.st_size);
	memset(data, 0, st.st_size);

	size_t count = 0;
	
	while (count < st.st_size) {
		size_t retval = fread(data, 1, st.st_size - count, fp);
		
		if (retval == 0) break;
		
		count += retval;
	}
	
	fclose(fp);
	
	if (count < st.st_size) {
		free(data);

		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Error reading source file.");
		}

		return false;
	}

	bool retval = putData(data, st.st_size, dest, failureMsg, successMsg);
	free(data);
	return retval;
}

bool PhoneInteraction::putPEMOnPhone(const char *pemFile)
{
	return putFile(pemFile, "/System/Library/Lockdown/iPhoneActivation.pem", NOTIFY_PUTPEM_FAILED,
				   NOTIFY_PUTPEM_SUCCESS);
}

bool PhoneInteraction::putServicesFileOnPhone(const char *servicesFile)
{
	return putFile(servicesFile, "/System/Library/Lockdown/Services.plist", NOTIFY_PUTSERVICES_FAILED,
				   NOTIFY_PUTSERVICES_SUCCESS);
}

bool PhoneInteraction::putFstabFileOnPhone(const char *fstabFile)
{
	return putFile(fstabFile, "/etc/fstab", NOTIFY_PUTFSTAB_FAILED, NOTIFY_PUTFSTAB_SUCCESS);
}

bool PhoneInteraction::putRingtoneOnPhone(const char *ringtoneFile)
{

	if (!isConnected()) {
		(*m_notifyFunc)(NOTIFY_PUTFILE_FAILED, "Can't write a ringtone when no phone is connected.");
		return false;
	}

	struct afc_directory *dir;
	const char *ringtoneDir = "/var/root/Library/Ringtones";

	if (AFCDirectoryOpen(m_hAFC, (char*)ringtoneDir, &dir)) {

		if (AFCDirectoryCreate(m_hAFC, (char*)ringtoneDir)) {
			(*m_notifyFunc)(NOTIFY_PUTFILE_FAILED, "Error creating directory for ringtones.");
			return false;
		}

		if (AFCDirectoryOpen(m_hAFC, (char*)ringtoneDir, &dir)) {
			(*m_notifyFunc)(NOTIFY_PUTFILE_FAILED, "Error opening directory for ringtones.");
			return false;
		}

	}

	AFCDirectoryClose(m_hAFC, dir);

	const char *ringtoneFilename = UtilityFunctions::getLastPathElement(ringtoneFile);
	char ringtonePath[PATH_MAX];
	strcpy(ringtonePath, ringtoneDir);
	strcat(ringtonePath, "/");
	strcat(ringtonePath, ringtoneFilename);

	if (!putFile(ringtoneFile, ringtonePath)) {
		(*m_notifyFunc)(NOTIFY_PUTFILE_FAILED, "Error putting ringtone file on phone.");
		return false;
	}

	(*m_notifyFunc)(NOTIFY_PUTFILE_SUCCESS, "Successfully put ringtone on phone.");
	return true;
}

bool PhoneInteraction::putWallpaperOnPhone(const char *wallpaperFile, const char *wallpaperThumb)
{
	struct afc_directory *dir;
	const char *wallpaperDir = "/var/root/Library/Wallpaper";
	
	if (AFCDirectoryOpen(m_hAFC, (char*)wallpaperDir, &dir)) {
		
		if (AFCDirectoryCreate(m_hAFC, (char*)wallpaperDir)) {
			(*m_notifyFunc)(NOTIFY_PUTFILE_FAILED, "Error creating directory for Wallpaper.");
			return false;
		}
		
		if (AFCDirectoryOpen(m_hAFC, (char*)wallpaperDir, &dir)) {
			(*m_notifyFunc)(NOTIFY_PUTFILE_FAILED, "Error opening directory for Wallpaper.");
			return false;
		}
		
	}
	
	AFCDirectoryClose(m_hAFC, dir);
	
	const char *wallpaperFilename = UtilityFunctions::getLastPathElement(wallpaperFile);
	char wallpaperPath[PATH_MAX];
	strcpy(wallpaperPath, wallpaperDir);
	strcat(wallpaperPath, "/");
	strcat(wallpaperPath, wallpaperFilename);

	if (!putFile(wallpaperFile, wallpaperPath)) {
		(*m_notifyFunc)(NOTIFY_PUTFILE_FAILED, "Error putting wallpaper file on phone.");
		return false;
	}

	memset(wallpaperPath, 0, PATH_MAX);
	wallpaperFilename = UtilityFunctions::getLastPathElement(wallpaperThumb);
	strcpy(wallpaperPath, wallpaperDir);
	strcat(wallpaperPath, "/");
	strcat(wallpaperPath, wallpaperFilename);

	if (!putFile(wallpaperFile, wallpaperPath)) {
		(*m_notifyFunc)(NOTIFY_PUTFILE_FAILED, "Error putting wallpaper thumbnail on phone.");
		return false;
	}
	
	(*m_notifyFunc)(NOTIFY_PUTFILE_SUCCESS, "Successfully put wallpaper on phone.");
	return true;
}

bool PhoneInteraction::writeDataToFile(void *buf, int size, const char *file,
									   int failureMsg, int successMsg)
{
    FILE *fp = fopen(file, "w");

    if (fp == NULL) {
		
		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Error opening file for writing.");
		}
		
		return false;
    }
	
	size_t count = 0;
	
	while (count < size) {
		size_t retval = fwrite(buf, 1, size - count, fp);
		
		if (retval == 0) break;
		
		count += retval;
	}
	
    fclose(fp);
	
	if (count < size) {
		
		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Error writing to file.");
		}
		
		return false;
	}
	
	return true;
}

int PhoneInteraction::getFileSize(const char *path)
{
    char *key, *val;
    unsigned int size;
    struct afc_dictionary *info;
	
    if (AFCFileInfoOpen(m_hAFC, (char*)path, &info)) {
        return -1;
	}
	
    while (1) {
        AFCKeyValueRead(info, &key, &val);
        if (!key || !val)
            break;
		
        if (!strcmp(key, "st_size")) {
            sscanf(val, "%u", &size);
            AFCKeyValueClose(info);
            return (int)size;
        }

    }

    AFCKeyValueClose(info);
    return -1;
}

bool PhoneInteraction::getFileData(void **buf, int *size, const char *file,
								   int failureMsg, int successMsg)
{

	if (!isConnected()) {
		
		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Can't read a file when no phone is connected.");
		}
		
		return false;
	}

	*size = getFileSize(file);

	if (*size == -1) {
		
		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Error getting phone file size.");
		}
		
		return false;
	}
	else if (*size == 0) {
		return true;
	}

	*buf = malloc(*size);

	afc_file_ref rAFC;
	
	if (AFCFileRefOpen(m_hAFC, (char*)file, 1, &rAFC)) {

		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Error getting phone file reference.");
		}
		
		return false;
	}

	unsigned int us_size = *size;

    if (AFCFileRefRead(m_hAFC, rAFC, (unsigned char*)(*buf), &us_size)) {
		AFCFileRefClose(m_hAFC, rAFC);
		free(*buf);
		
		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Error reading from phone file.");
		}
		
		return false;
    }
	
	if (AFCFileRefClose(m_hAFC, rAFC)) {
		free(*buf);

		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Error closing phone file.");
		}
		
		return false;
	}

	return true;
}

bool PhoneInteraction::getFile(const char *src, const char *dest, int failureMsg,
							   int successMsg)
{

	if (!isConnected()) {
		
		if (failureMsg) {
			(*m_notifyFunc)(failureMsg, "Can't read a file when no phone is connected.");
		}
		
		return false;
	}

	void *buf;
	int size;

	if (!getFileData(&buf, &size, src, failureMsg, successMsg)) {
		return false;
	}

	if (size == -1) {
		return false;
	}
	else if (size == 0) {
		FILE *fp = fopen(dest, "w");
		fclose(fp);
		return true;
	}

	bool retval = writeDataToFile(buf, size, dest, failureMsg, successMsg);

	free(buf);

	return retval;
}

bool PhoneInteraction::getActivationFile(const char *dest)
{

	if (getFile("/var/root/Library/Lockdown/activation_record.plist", dest, 0, 0)) {
		(*m_notifyFunc)(NOTIFY_GET_ACTIVATION_SUCCESS, "Successfully saved activation file.");
		return true;
	}

	afc_directory *dir;

	if (AFCDirectoryOpen(m_hAFC, "/var/root/Library/Lockdown/activation_records", &dir)) {
		(*m_notifyFunc)(NOTIFY_GET_ACTIVATION_FAILED, "Can't find activation record.");
		return false;
	}

	char *filename = NULL;

	while (1) {

		if (AFCDirectoryRead(m_hAFC, dir, &filename)) {
			AFCDirectoryClose(m_hAFC, dir);
			(*m_notifyFunc)(NOTIFY_GET_ACTIVATION_FAILED, "Can't find activation record.");
			return false;
		}

		if (filename == NULL) {
			break;
		}

		int len = strlen(filename);

		if (len < 6) {
			continue;
		}

		if (!strcmp(filename + (len-6), ".plist")) {
			break;
		}

	}

	if (filename == NULL) {
		AFCDirectoryClose(m_hAFC, dir);
		(*m_notifyFunc)(NOTIFY_GET_ACTIVATION_FAILED, "Can't find activation record.");
		return false;
	}

	char filepath[PATH_MAX];
	memset(filepath, 0, PATH_MAX);
	strcpy(filepath, "/var/root/Library/Lockdown/activation_records/");
	strcat(filepath, filename);

	if (!getFile(filepath, dest, NOTIFY_GET_ACTIVATION_FAILED, 0)) {
		AFCDirectoryClose(m_hAFC, dir);
		return false;
	}

	AFCDirectoryClose(m_hAFC, dir);
	(*m_notifyFunc)(NOTIFY_GET_ACTIVATION_SUCCESS, "Successfully saved activation file.");
	return true;
}

bool PhoneInteraction::copyPhoneFilesystem(const char *dirpath, const char *dest,
										   bool ignoreUserFiles)
{

	if (!isConnected()) {
		(*m_notifyFunc)(NOTIFY_FSCOPY_FAILED, "Can't copy when no phone is connected.");
		return false;
	}

	if (m_statusFunc) {
		(*m_statusFunc)("Copying phone filesystem...", true);
	}

	bool retval = copyFilesystemRecursive(m_hAFC, dirpath, dest, dest, ignoreUserFiles);

	if (retval) {
		(*m_notifyFunc)(NOTIFY_FSCOPY_SUCCESS, "Filesystem copy succeeded.");
	}

	return retval;
}

bool PhoneInteraction::copyFilesystemRecursive(afc_connection *conn, const char *dirpath,
											   const char *dest, const char *basepath,
											   bool ignoreUserFiles)
{
	afc_directory *dir;

	if (AFCDirectoryOpen(conn, (char*)dirpath, &dir)) {
		(*m_notifyFunc)(NOTIFY_FSCOPY_FAILED, "Error opening directory on phone.");
		return false;
	}

	char phoneWorkingDir[PATH_MAX];
	char computerWorkingDir[PATH_MAX];
	char *fileName = NULL;

	strcpy(phoneWorkingDir, dirpath);
	strcpy(computerWorkingDir, dest);

	while (1) {
		AFCDirectoryRead(conn, dir, &fileName);

		if (fileName == NULL) break;

		if (strcmp(fileName, ".") && strcmp(fileName, "..") && strcmp(fileName, "/etc")) {
			char filePath[PATH_MAX];
			int len = strlen(phoneWorkingDir);
			struct afc_dictionary *info;
			char *key, *val;

			strcpy(filePath, phoneWorkingDir);

			if (filePath[len-1] != '/') {
				strcat(filePath, "/");
			}

			strcat(filePath, fileName);

#ifdef DEBUG
			printf("reading phone path: %s\n", filePath);
#endif

			bool isSymlink = false;

			// TODO: Figure out how to get symlinks on Windows
#if defined(__APPLE__)
			// this is an ugly hack because I can't tell if a file/dir is
			// a symlink or not.  so we compare it against a known list
			for (int i = 0; i < g_numSymlinks; i++) {

				if (!strcmp(g_symlinks[i], filePath)) {
					char origPath[PATH_MAX];
					strcpy(origPath, basepath);
					strcat(origPath, g_symlinkOriginals[i]);

					char linkPath[PATH_MAX];
					strcpy(linkPath, basepath);
					strcat(linkPath, g_symlinks[i]);

#ifdef DEBUG
					printf("creating symlink from %s to %s\n", linkPath, origPath);
#endif

					if (symlink(origPath, linkPath)) {
						AFCDirectoryClose(conn, dir);
						(*m_notifyFunc)(NOTIFY_FSCOPY_FAILED, "Error creating symlink.");
						return false;
					}

					isSymlink = true;
					break;
				}

			}
#endif

			if (isSymlink) continue;

			if (AFCFileInfoOpen(conn, filePath, &info)) {
				AFCDirectoryClose(conn, dir);
				(*m_notifyFunc)(NOTIFY_FSCOPY_FAILED, "Error opening file info.");
				return false;
			}

			while (1) {
				AFCKeyValueRead(info, &key, &val);

				if (!key || !val)
					break;

				//printf("key = %s, val = %s\n", key, val);

				if (strcmp(key, "st_ifmt")) continue;

				if (!strcmp(val, "S_IFDIR")) {
					bool ignore = false;

					if (ignoreUserFiles) {
						
						if (!strncmp(filePath, "/private/var/root/Media", 23)) {
							ignore = true;
						}
						else if (!strncmp(filePath, "/private/var/root/Library", 25)) {
							ignore = true;
							char newPath[PATH_MAX];
							len = strlen(computerWorkingDir);
							strcpy(newPath, computerWorkingDir);
							
							if (newPath[len-1] != '/') {
								strcat(newPath, "/");
							}
							
							strcat(newPath, "/private/var/root/Library");
#if defined(WIN32)
							mkdir(newPath);
#else
							mkdir(newPath, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
#endif

							memset(newPath, 0, PATH_MAX);
							strcpy(newPath, computerWorkingDir);

							if (newPath[len-1] != '/') {
								strcat(newPath, "/");
							}
							
							strcat(newPath, "/private/var/root/Library/Preferences");
#if defined(WIN32)
							mkdir(newPath);
#else
							mkdir(newPath, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
#endif
						}
						
					}

					if (!ignore) {
						char newPath[PATH_MAX];
						len = strlen(computerWorkingDir);
						strcpy(newPath, computerWorkingDir);
						
						if (newPath[len-1] != '/') {
							strcat(newPath, "/");
						}
						
						strcat(newPath, fileName);
#if defined(WIN32)
						mkdir(newPath);
#else
						mkdir(newPath, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
#endif
						
						if (!copyFilesystemRecursive(conn, filePath, newPath, basepath,
													 ignoreUserFiles)) {
							AFCDirectoryClose(conn, dir);
							return false;
						}
						
					}
#ifdef DEBUG
					else {
						printf("ignoring user file\n");
					}
#endif

				}
				else if (!strcmp(val, "S_IFREG")) {
					bool ignore = false;

					if (ignoreUserFiles) {

						if (!strcmp(filePath, "/com.apple.itunes.lock_sync")) {
							ignore = true;
						}

					}

					if (!ignore) {
						char newPath[PATH_MAX];
						len = strlen(computerWorkingDir);
						strcpy(newPath, computerWorkingDir);
						
						if (newPath[len-1] != '/') {
							strcat(newPath, "/");
						}
						
						strcat(newPath, fileName);
						
						if (!getFile(filePath, newPath, NOTIFY_FSCOPY_FAILED,
									 NOTIFY_FSCOPY_SUCCESS)) {
							AFCDirectoryClose(conn, dir);
							return false;
						}
						
					}

				}
#ifdef DEBUG
				else if (!strcmp(val, "S_IFCHR") || !strcmp(val, "S_IFBLK") || !strcmp(val, "S_IFSOCK")) {
					// special files, don't do anything
					printf("ignoring device or socket file\n");
				}
				else {
					printf("unknown file type: %s\n", val);
				}
#endif

			}
			
			AFCKeyValueClose(info);
		}

	}

	AFCDirectoryClose(conn, dir);
	return true;
}

bool PhoneInteraction::createDirectory(const char *dir)
{

	if (!isConnected()) {
		(*m_notifyFunc)(NOTIFY_CREATE_DIR_FAILED, "Can't create a directory when no phone is connected.");
		return false;
	}

	if (AFCDirectoryCreate(m_hAFC, (char*)dir)) {
		(*m_notifyFunc)(NOTIFY_CREATE_DIR_FAILED, "Error creating directory.");
		return false;
	}
	
	(*m_notifyFunc)(NOTIFY_CREATE_DIR_SUCCESS, "Directory successfully created on phone.");
	return true;
}

bool PhoneInteraction::removePath(const char *path)
{
	
	if (!isConnected()) {
		(*m_notifyFunc)(NOTIFY_REMOVE_PATH_FAILED, "Can't remove a file/directory when no phone is connected.");
		return false;
	}

	if (AFCRemovePath(m_hAFC, (char*)path)) {
		(*m_notifyFunc)(NOTIFY_REMOVE_PATH_FAILED, "Error removing file/directory.");
		return false;
	}

	(*m_notifyFunc)(NOTIFY_REMOVE_PATH_SUCCESS, "File/directory successfully removed from phone.");
	return true;
}

void PhoneInteraction::performJailbreak(const char *firmwarePath, const char *modifiedFstabPath,
										const char *modifiedServicesPath)
{

	if (!isConnected()) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Can't perform a jailbreak when no phone is connected.");
		return;
	}

	if (m_statusFunc) {
		(*m_statusFunc)("Performing jailbreak...", true);
	}

	if (!putFile(modifiedFstabPath, "fstab", 0, 0)) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error writing modified fstab to phone.");
		return;
	}

	if (!putFile(modifiedServicesPath, "Services.plist", 0, 0)) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error writing modified Services.plist to phone.");
		return;
	}

	if (m_firmwarePath != NULL) {
		free(m_firmwarePath);
	}

	size_t len = strlen(firmwarePath);

	if (firmwarePath[len-1] != '/') {
		m_firmwarePath = (char*)malloc(len + 2);
		strncpy(m_firmwarePath, firmwarePath, len);
		m_firmwarePath[len] = '/';
		m_firmwarePath[len+1] = 0;
	}
	else {
		m_firmwarePath = (char*)malloc(len + 1);
		strncpy(m_firmwarePath, firmwarePath, len);
		m_firmwarePath[len] = 0;
	}

	m_waitingForRecovery = true;
	(*m_notifyFunc)(NOTIFY_JAILBREAK_RECOVERY_WAIT, "Please press and hold Home + Sleep on your iPhone for 25 seconds...");

	// this would be the preferred way to put the iPhone in recovery mode, but things
	// seem to go bad when i use it
	/*
	if (AMDeviceEnterRecovery(m_iPhone)) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error entering recovery mode.");
		return;
	}
	 */

}

void PhoneInteraction::cancelJailbreak(bool userCouldntHoldOn)
{

	if (m_waitingForRecovery) {
		m_waitingForRecovery = false;
	}

	if (userCouldntHoldOn) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAIL_USER_COULDNT_HOLD, "Jailbreak failed (couldn't hold it eh?)");
	}
	else {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_CANCEL, "Jailbreak cancelled");
	}

}

void PhoneInteraction::jailbreakFinished()
{
	m_finishingJailbreak = false;
	(*m_notifyFunc)(NOTIFY_JAILBREAK_SUCCESS, "Jailbreak succeeded!");
}

bool PhoneInteraction::isPhoneJailbroken()
{
	return m_jailbroken;
}

void PhoneInteraction::recoveryModeStarted(am_recovery_device *dev)
{
	m_waitingForRecovery = false;
	m_inRecoveryMode = true;
	m_recoveryDevice = dev;
	(*m_notifyFunc)(NOTIFY_JAILBREAK_RECOVERY_CONNECTED, "Recovery mode started");

#if defined(__APPLE__)
	CFMutableDictionaryRef restoreOptions = AMRestoreCreateDefaultOptions(kCFAllocatorDefault);

	if (restoreOptions == NULL) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error creating restore options dictionary.");
		return;
	}

	CFStringRef cfFirmwarePath = CFStringCreateWithCString(kCFAllocatorDefault, m_firmwarePath,
														   kCFStringEncodingUTF8);

	if (cfFirmwarePath == NULL) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error creating path CFString.");
		return;
	}

    CFDictionarySetValue(restoreOptions, CFSTR("CreateFilesystemPartitions"), kCFBooleanFalse);
	CFDictionarySetValue(restoreOptions, CFSTR("RestoreBundlePath"), cfFirmwarePath);
    //CFDictionarySetValue(restoreOptions, CFSTR("RestoreBootArgs"), CFSTR("rd=md0 -progress"));
	
	// alternative boot args for restore mode boot spew
	CFDictionarySetValue(restoreOptions, CFSTR("RestoreBootArgs"), CFSTR("rd=md0 -v"));

	m_switchingToRestoreMode = true;

	if (AMRestorePerformRecoveryModeRestore(dev, restoreOptions, (void*)recoveryProgressCallback, NULL)) {
		CFRelease(cfFirmwarePath);
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error switching to restore mode.");
		return;
	}

	CFRelease(cfFirmwarePath);
#else

	// Another way to get into restore mode
	char ramdisk[PATH_MAX];
	int len = strlen(m_firmwarePath);

	strcpy(ramdisk, m_firmwarePath);

	if (ramdisk[len-1] != '/') {
		strcat(ramdisk, "/");
	}

	strcat(ramdisk, "694-5259-38.dmg");

	// check the firmware version
	struct stat st;

	if (stat(ramdisk, &st) == -1) {
		memset(ramdisk, 0, PATH_MAX);
		strcpy(ramdisk, m_firmwarePath);
		
		if (ramdisk[len-1] != '/') {
			strcat(ramdisk, "/");
		}

		// version 1.0.1 restore disk image (note that this will not load for some reason)
		strcat(ramdisk, "694-5281-6.dmg");

		if (stat(ramdisk, &st) == -1) {
			(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Unknown firmware version.");
			return;
		}

	}

	CFStringRef cfRamdisk = CFStringCreateWithCString(kCFAllocatorDefault, ramdisk,
													  kCFStringEncodingUTF8);

	if (cfRamdisk == NULL) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error creating path CFString.");
		return;
	}

	// send ramdisk file
	if (sendFileToDevice(dev, cfRamdisk)) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error sending ramdisk file to device.");
		return;
	}

	// load ramdisk
	if (sendCommandToDevice(dev, CFSTR("ramdisk"))) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error sending ramdisk command to device.");
		return;
	}

	memset(ramdisk, 0, PATH_MAX);
	strcpy(ramdisk, m_firmwarePath);
	
	if (ramdisk[len-1] != '/') {
		strcat(ramdisk, "/");
	}
	
	strcat(ramdisk, "kernelcache.restore.release.s5l8900xrb");

	CFStringRef cfKernCache = CFStringCreateWithCString(kCFAllocatorDefault, ramdisk,
														kCFStringEncodingUTF8);

	if (cfKernCache == NULL) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error creating path CFString.");
		return;
	}
	
	// send kernel cache file
	if (sendFileToDevice(dev, cfKernCache)) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error kernel cache file to device.");
		return;
	}

	// set boot args
	if (sendCommandToDevice(dev, CFSTR("setenv boot-args rd=md0 -v"))) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error setting boot args.");
		return;
	}

	// extra fancy stuff
	 
	// turn off autoboot
	//sendCommandToDevice(dev, CFSTR("setenv auto-boot false"));

	// no picture
	//sendCommandToDevice(dev, CFSTR("setpicture 0"));

	// black background
	//sendCommandToDevice(dev, CFSTR("bgcolor 0 0 0"));

	m_switchingToRestoreMode = true;

	// boot kernel cache to get to restore mode
	if (sendCommandToDevice(dev, CFSTR("bootx"))) {
		(*m_notifyFunc)(NOTIFY_JAILBREAK_FAILED, "Error rebooting device.");
		return;
	}
#endif

}

void PhoneInteraction::recoveryModeFinished(am_recovery_device *dev)
{
	m_inRecoveryMode = false;
	m_recoveryDevice = NULL;
	(*m_notifyFunc)(NOTIFY_JAILBREAK_RECOVERY_DISCONNECTED, "Recovery mode ended");
}

void PhoneInteraction::restoreModeStarted(am_device *dev)
{
	m_inRestoreMode = true;
	(*m_notifyFunc)(NOTIFY_JAILBREAK_RESTORE_CONNECTED, "Restore mode started");

    struct am_restore_device *rdev = AMRestoreModeDeviceCreate(0, AMDeviceGetConnectionID(dev), 0);
    rdev->port = socketForPort(rdev, 0xf27e);

	sleep(28);

	CFMutableDictionaryRef request = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
															   &kCFTypeDictionaryKeyCallBacks,
															   &kCFTypeDictionaryValueCallBacks);

	if (request == NULL) {
		return;
	}

	// fs checks
	CFDictionarySetValue(request, CFSTR("Operation"), CFSTR("FilesystemCheck"));
	CFDictionarySetValue(request, CFSTR("DeviceName"), CFSTR("/dev/disk0s1"));
	performOperation(rdev, request);
	sleep(3);

	request = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
										&kCFTypeDictionaryValueCallBacks);

	if (request == NULL) {
		return;
	}

	CFDictionarySetValue(request, CFSTR("Operation"), CFSTR("FilesystemCheck"));
	CFDictionarySetValue(request, CFSTR("DeviceName"), CFSTR("/dev/disk0s2"));
	performOperation(rdev, request);
	sleep(3);

	// mount disks
	request = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
										&kCFTypeDictionaryValueCallBacks);
	
	if (request == NULL) {
		return;
	}
	
	CFDictionarySetValue(request, CFSTR("Operation"), CFSTR("Mount"));
	CFDictionarySetValue(request, CFSTR("DeviceName"), CFSTR("/dev/disk0s1"));
	CFDictionarySetValue(request, CFSTR("MountPoint"), CFSTR("/mnt1"));
	performOperation(rdev, request);

	request = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
										&kCFTypeDictionaryValueCallBacks);
	
	if (request == NULL) {
		return;
	}

	CFDictionarySetValue(request, CFSTR("Operation"), CFSTR("Mount"));
	CFDictionarySetValue(request, CFSTR("DeviceName"), CFSTR("/dev/disk0s2"));
	CFDictionarySetValue(request, CFSTR("MountPoint"), CFSTR("/mnt2"));
	performOperation(rdev, request);

	// copy edited files to main fs
	request = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
										&kCFTypeDictionaryValueCallBacks);
	
	if (request == NULL) {
		return;
	}

	CFDictionarySetValue(request, CFSTR("Operation"), CFSTR("Ditto"));
	CFDictionarySetValue(request, CFSTR("SourcePath"), CFSTR("/mnt2/root/Media/fstab"));
	CFDictionarySetValue(request, CFSTR("DestinationPath"), CFSTR("/mnt1/etc/fstab"));
	performOperation(rdev, request);

	request = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
										&kCFTypeDictionaryValueCallBacks);
	
	if (request == NULL) {
		return;
	}

	CFDictionarySetValue(request, CFSTR("Operation"), CFSTR("Ditto"));
	CFDictionarySetValue(request, CFSTR("SourcePath"), CFSTR("/mnt2/root/Media/Services.plist"));
	CFDictionarySetValue(request, CFSTR("DestinationPath"), CFSTR("/mnt1/System/Library/Lockdown/Services.plist"));
	performOperation(rdev, request);

	// clean up
	request = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
										&kCFTypeDictionaryValueCallBacks);
	
	if (request == NULL) {
		return;
	}

	CFDictionarySetValue(request, CFSTR("Operation"), CFSTR("RemovePath"));
	CFDictionarySetValue(request, CFSTR("Path"), CFSTR("/mnt2/root/Media/fstab"));
	performOperation(rdev, request);

	request = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
										&kCFTypeDictionaryValueCallBacks);
	
	if (request == NULL) {
		return;
	}

	CFDictionarySetValue(request, CFSTR("Operation"), CFSTR("RemovePath"));
	CFDictionarySetValue(request, CFSTR("Path"), CFSTR("/mnt2/root/Media/Services.plist"));
	performOperation(rdev, request);

	// unmount disks
	request = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
										&kCFTypeDictionaryValueCallBacks);
	
	if (request == NULL) {
		return;
	}

	CFDictionarySetValue(request, CFSTR("Operation"), CFSTR("Unmount"));
	CFDictionarySetValue(request, CFSTR("MountPoint"), CFSTR("/mnt2"));
	performOperation(rdev, request);

	request = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
										&kCFTypeDictionaryValueCallBacks);
	
	if (request == NULL) {
		return;
	}

	CFDictionarySetValue(request, CFSTR("Operation"), CFSTR("Unmount"));
	CFDictionarySetValue(request, CFSTR("MountPoint"), CFSTR("/mnt1"));
	performOperation(rdev, request);

	// reboot
	request = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
										&kCFTypeDictionaryValueCallBacks);
	
	if (request == NULL) {
		return;
	}

	CFDictionarySetValue(request, CFSTR("Operation"), CFSTR("Goodbye"));
	performOperation(rdev, request);

	m_finishingJailbreak = true;
}

void PhoneInteraction::restoreModeFinished(am_device *dev)
{
	m_inRestoreMode = false;
	(*m_notifyFunc)(NOTIFY_JAILBREAK_RESTORE_DISCONNECTED, "Restore mode ended");
}

int PhoneInteraction::sendCommandToDevice(am_recovery_device *rdev, CFStringRef cfs)
{
	int retval = 0;
#if defined (WIN32)
	asm("movl %3, %%esi\n\tpush %1\n\tcall *%0\n\tmovl %%eax, %2"
		:
		:"m"(_sendCommandToDevice),  "m"(cfs), "m"(retval), "m"(rdev)
		:);
#elif defined (__APPLE__)
	retval = _sendCommandToDevice(rdev, cfs);
#else
	retval = 1;
	printf("sendCommandToDevice not implemented on your platform\n");
#endif
    return retval;
}

int PhoneInteraction::sendFileToDevice(am_recovery_device *rdev, CFStringRef filename)
{
	int retval = 0;
#if defined (WIN32)
	asm("movl %3, %%ecx\n\tpush %1\n\tcall *%0\n\tmovl %%eax, %2"
		:
		:"m"(_sendFileToDevice),  "m"(filename), "m"(retval), "m"(rdev)
		:);
#elif defined (__APPLE__)
	retval = _sendFileToDevice(rdev, filename);
#else
	retval = 1;
	printf("sendFileToDevice not implemented on your platform\n");
#endif
	return retval;
	
}

int PhoneInteraction::performOperation(am_restore_device *rdev, CFMutableDictionaryRef message)
{
	int retval = 0;
// TODO: This crashes on Win32.  However, it's identical to jailbreak (which does work)
// aside from the size of the function arguments (8 bytes instead of 4).  Not sure how to fix that.
#if defined (WIN32)
	//asm("movl %1, %%ebx\n\tmovl %3, %%esi\n\tcall *%0\n\tmovl %%eax, %2"
	asm("movl %3, %%esi\n\tmovl %1, %%ebx\n\tcall *%0\n\tmovl %%eax, %2"
		:
		:"m"(_performOperation), "m"(message), "m"(retval), "m"(rdev)
		:);
#elif defined (__APPLE__)
	retval = _performOperation(rdev, message);
#else
	retval = 1;
	printf("performOperation not implemented on your platform\n");
#endif
	return retval;
}

int PhoneInteraction::socketForPort(am_restore_device *rdev, unsigned int portnum)
{
	int retval = 0;
#if defined (WIN32)
	asm("push %1\n\tpush %3\n\tcall *%0\n\tmovl %%eax, %2"
		:
		:"m"(_socketForPort), "g"(portnum), "m"(retval), "m"(rdev)
		:);
#elif defined (__APPLE__)
    retval = _socketForPort(rdev, portnum);
#else
	retval = 1;
	printf("socketForPort not implemented on your platform\n");
#endif
	return retval;
}
